import os
import time
import json
import logging
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives import hashes
from cryptography import x509
from acme import challenges, client, messages
from acme.client import ClientNetwork
from josepy import JWKRSA
from aliyunsdkcore.client import AcsClient
from aliyunsdkalidns.request.v20150109 import AddDomainRecordRequest, DeleteDomainRecordRequest
import dns.resolver

# 启用详细日志
logging.basicConfig(level=logging.INFO)

# 配置信息
CONFIG = {
    "aliyun_access_key_id": "",
    "aliyun_access_secret": "",
    "domain": "furpass.cn",
    "email": "service@ghink.net",
    "acme_directory_url": "https://acme-v02.api.letsencrypt.org/directory",
    "dns_servers": ["8.8.8.8"]
}

# 初始化阿里云客户端
aliyun_client = AcsClient(
    CONFIG['aliyun_access_key_id'],
    CONFIG['aliyun_access_secret'],
    'cn-hangzhou'
)

def generate_key_pair(key_size=2048):
    """生成账户密钥和证书密钥"""
    return (
        rsa.generate_private_key(public_exponent=65537, key_size=key_size),
        rsa.generate_private_key(public_exponent=65537, key_size=key_size)
    )

def create_csr(private_key, domains):
    """创建 CSR 请求（带 SAN 扩展验证）"""
    builder = x509.CertificateSigningRequestBuilder()
    builder = builder.subject_name(
        x509.Name([x509.NameAttribute(x509.NameOID.COMMON_NAME, domains[0])])
    )
    san_names = [x509.DNSName(domain) for domain in domains]
    san = x509.SubjectAlternativeName(san_names)
    builder = builder.add_extension(san, critical=False)
    csr = builder.sign(private_key, hashes.SHA256())
    
    # 调试：打印 CSR 中的域名
    csr_pem = csr.public_bytes(serialization.Encoding.PEM).decode()
    logging.debug(f"CSR 内容:\n{csr_pem}")
    return csr

def handle_dns_challenge(domain, validation):
    """添加 DNS TXT 记录（带重试机制）"""
    for _ in range(3):  # 重试3次
        try:
            req = AddDomainRecordRequest.AddDomainRecordRequest()
            req.set_DomainName(domain)
            req.set_Type("TXT")
            req.set_RR("_acme-challenge")
            req.set_Value(f'"{validation}"')
            resp = aliyun_client.do_action_with_exception(req)
            resp_data = json.loads(resp.decode('utf-8'))
            logging.info(f"DNS记录添加成功: {resp_data}")
            return resp_data['RecordId']
        except Exception as e:
            logging.warning(f"添加记录失败: {str(e)}，重试中...")
            time.sleep(5)
    raise RuntimeError("无法添加DNS记录")

def wait_for_propagation(domain, validation, max_retries=30):
    """使用权威 DNS 检查传播"""
    resolver = dns.resolver.Resolver()
    resolver.nameservers = CONFIG['dns_servers']  # 强制使用阿里云DNS
    
    for i in range(max_retries):
        try:
            answers = resolver.resolve(f'_acme-challenge.{domain}', 'TXT')
            found = any(validation in rdata.to_text() for rdata in answers)
            if found:
                logging.info(f"DNS验证成功 (第{i+1}次检查)")
                return True
        except dns.resolver.NXDOMAIN:
            pass
        logging.debug(f"DNS记录未生效，等待10秒后重试... ({i+1}/{max_retries})")
        time.sleep(10)
    raise TimeoutError("DNS记录未在预期时间内生效")

def delete_dns_record(record_id):
    """删除 DNS 记录"""
    if not record_id:
        return
    req = DeleteDomainRecordRequest.DeleteDomainRecordRequest()
    req.set_RecordId(record_id)
    try:
        aliyun_client.do_action_with_exception(req)
    except Exception as e:
        print(f"删除记录 {record_id} 失败: {str(e)}")

def main():
    # 生成独立密钥对
    account_key, cert_key = generate_key_pair()
    
    # 初始化ACME客户端
    jwk_account = JWKRSA(key=account_key)
    net = ClientNetwork(jwk_account, user_agent="ACME-Auto-Cert/1.0")
    directory = client.ClientV2.get_directory(CONFIG['acme_directory_url'], net)
    acme = client.ClientV2(directory, net=net)
    
    # 注册账户
    account = acme.new_account(
        messages.NewRegistration(
            terms_of_service_agreed=True,
            contact=("mailto:" + CONFIG['email'],)
        )
    )
    
    # 创建 CSR
    domains = [f"*.{CONFIG['domain']}", CONFIG['domain']]
    csr = create_csr(cert_key, domains)
    
    # 提交订单
    order = acme.new_order(csr.public_bytes(serialization.Encoding.PEM))
    logging.info(f"订单创建成功: {order.uri}")
    
    record_ids = []
    try:
        # 处理所有挑战
        for authz in order.authorizations:
            domain = authz.body.identifier.value
            logging.info(f"处理域名授权: {domain}")
            
            for challenge in authz.body.challenges:
                if isinstance(challenge.chall, challenges.DNS01):
                    # 计算验证值
                    validation = challenge.chall.validation(acme.net.key)
                    logging.info(f"需要添加的TXT记录值: {validation}")
                    
                    # 添加DNS记录
                    record_id = handle_dns_challenge(CONFIG['domain'], validation)
                    record_ids.append(record_id)
                    
                    # 提交挑战应答
                    response = challenge.response(acme.net.key)
                    acme.answer_challenge(challenge, response)
                    logging.info(f"挑战应答已提交: {challenge.uri}")
        
        # 等待所有DNS生效
        for authz in order.authorizations:
            domain = authz.body.identifier.value
            for challenge in authz.body.challenges:
                if isinstance(challenge.chall, challenges.DNS01):
                    validation = challenge.chall.validation(acme.net.key)
                    wait_for_propagation(domain, validation)
        
        # 最终确认订单
        order = acme.poll_and_finalize(order)  # 延长等待时间
        logging.info(f"证书签发成功: {order.fullchain_pem[:100]}...")
        
        # 保存证书
        os.makedirs("certs", exist_ok=True)
        with open("certs/fullchain.pem", "wb") as f:
            f.write(order.fullchain_pem.encode())
        with open("certs/privkey.pem", "wb") as f:
            f.write(cert_key.private_bytes(
                encoding=serialization.Encoding.PEM,
                format=serialization.PrivateFormat.TraditionalOpenSSL,
                encryption_algorithm=serialization.NoEncryption()
            ))
    except Exception as e:
        logging.error(f"证书申请失败: {str(e)}")
        raise
    finally:
        # 清理DNS记录
        logging.info("开始清理DNS记录...")
        for record_id in record_ids:
            try:
                delete_dns_record(record_id)
                logging.info(f"记录 {record_id} 已删除")
            except Exception as e:
                logging.error(f"删除记录失败: {str(e)}")

if __name__ == "__main__":
    main()